package fr.xebia.monitoring;

import java.net.URL;
import java.util.List;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.UrlResource;

import fr.xebia.monitoring.appender.MeasureAppenderManager;
import fr.xebia.monitoring.config.AppenderConfig;
import fr.xebia.monitoring.config.MonitorConfigManager;
import fr.xebia.monitoring.config.MonitoringConfig;
import fr.xebia.monitoring.config.MonitoringConfigLoader;

/**
 * TODO Delete the directory src/main/webapp, usefull only for test purpose
 * TODO Change also the packaging type in the pom.xml
 */
public class MonitorFactory implements MonitorProvider {

	protected static Logger logger = Logger.getLogger(MonitorFactory.class);

	private static final DisabledMonitor DISABLED_MONITOR = new DisabledMonitor();

	private static XmlBeanFactory factory;
	private static Cache monitorCache;
	private static MeasureHandler measureHandler;

	private static boolean monitoringEnabled = false;
	private static MonitoringConfig monitoringConfig;
	private static MonitorConfigManager monitorConfigManager;
	private static MeasureAppenderManager measureAppenderManager;

	public static boolean isMonitoringEnabled() {
		return MonitorFactory.monitoringEnabled;
	}

	public static void setMonitoringEnabled(boolean monitoringEnabled) {
		MonitorFactory.monitoringEnabled = monitoringEnabled;
		if (MonitorFactory.monitoringEnabled) {
			init();
		} else {
			destroy();
		}
	}

	public static synchronized void init() {
		init(null);
	}

	public static synchronized void init(String configFilename) {
		long start = System.currentTimeMillis();
		try {
			if (monitoringConfig == null) { // Try to read the configuration
				monitoringConfig = new MonitoringConfigLoader().configure(configFilename);
				monitoringEnabled = monitoringConfig.getRoot().isEnable();
			}
			if (monitoringEnabled) {
				URL springConfigUrl = Thread.currentThread().getContextClassLoader().getResource("monitoring-beans.xml");
				factory = new XmlBeanFactory(new UrlResource(springConfigUrl));
				factory.preInstantiateSingletons();
				measureHandler = (MeasureHandler) factory.getBean("measureHandler");

				monitorCache = CacheManager.getInstance().getCache(MonitorFactory.class.getName());

				configureAppenders(monitoringConfig.getAppender());
				configureMonitoring(monitoringConfig);
			}
		} catch (Exception e) {
			destroy();
			logger.error("Error configuring monitoring. Monitoring is disabled", e);
		} finally {
			long end = System.currentTimeMillis();
			logger.info("Initalization done in "+(end-start)+" ms");
		}
	}

	private static void configureMonitoring(MonitoringConfig monitoringConfig) {
		monitorConfigManager = new MonitorConfigManager();
		monitorConfigManager.configure(monitoringConfig);
	}

	private static void configureAppenders(List<AppenderConfig> appenders) {
		measureAppenderManager = (MeasureAppenderManager) factory.getBean("measureAppenderManager");
		measureAppenderManager.configureAppenders(appenders);
		measureAppenderManager.startAppenders();
	}

	public static synchronized void destroy() {
		logger.info("Destroying resources");
		if (factory != null) {
			factory.destroySingletons();
		}
		factory = null;
		CacheManager.getInstance().removeCache(MonitorFactory.class.getName());
		monitorCache = null;
		monitoringConfig = null;
		measureHandler = null;
		if (measureAppenderManager != null) {
			measureAppenderManager.stopAppenders();
		}
	}

	public static void enableMonitor(String categoryName) {
		if (monitoringEnabled == false) {
			return ;
		}
		monitorConfigManager.enableOrDisableMonitor(categoryName, true);
	}

	public static void disableMonitor(String categoryName) {
		if (monitoringEnabled == false) {
			return ;
		}
		monitorConfigManager.enableOrDisableMonitor(categoryName, false);
	}
	
	public static boolean isCategoryEnabled(String categoryName) {
		if (monitoringEnabled == false) {
			return false;
		}
		if (monitorConfigManager != null) {
			return monitorConfigManager.isCategoryEnabled(categoryName);
		}
		return false;
	}

	public static Monitor start(Class<?> clazz) {
		return start(clazz.getName());
	}

	public static Monitor start(String... args) {
		if (factory == null) {
			init();
		}
		String category = StringUtils.join(args, '.');
		if (isCategoryEnabled(category) == false) {
			return DISABLED_MONITOR;
		}
		return getMonitor(category);
	}

	private static Monitor getMonitor(String category) {
		ActiveMonitor activeMonitor;
		if (monitorCache != null && monitorCache.isKeyInCache(category)) {
			activeMonitor = (ActiveMonitor) monitorCache.get(category).getObjectValue();
		} else {
			activeMonitor = new ActiveMonitor(category);
			activeMonitor.setMeasureCollector(measureHandler);
			if (monitorCache != null) {
				monitorCache.put(new Element(category, activeMonitor), true);
			}
		}
		activeMonitor.start();
		return activeMonitor;
	}

	public static MeasureHandler getMeasureHandler() {
		if (factory == null) {
			init();
		}
		if (factory == null) {
			// An configuration error has occurred
			throw new MonitoringException("Monitoring initialization failed");
		}
		return measureHandler;
	}

	private MonitorFactory() {
	}
}
